#include <stdio.h>
#include <stdlib.h>
#include <omp.h>


void parallel_histogram(float *array, int n, int *bins, int num_bins)
{
    int i;
    /* Initialize the bins as zero */
    for (i = 0; i < num_bins; i++) {
        bins[i] = 0; 
    }
    /* Counting */
    int idx;
    #pragma omp parallel private(i, idx) num_threads(8)
    {
        int *local_bins = (int*)malloc(sizeof(int) * num_bins);
        for (i = 0; i < num_bins; i++) {
            local_bins[i] = 0; 
        }
        # pragma omp for 
        for (i = 0; i < n; i++) {
            int val = (int)array[i];
            if (val == num_bins) { /* Ensure 10 numbers go to the last bin */
                idx = num_bins - 1;
            } else {
                idx = val % num_bins;
            }
            local_bins[idx]++;
        }
        # pragma omp critical
        {
            for (i = 0; i < num_bins; i++) {
                bins[i] += local_bins[i];
            }
        }
        free(local_bins);
    }
}

void generate_random_numbers(float *array, int n) 
{
    int i;
    float a = 10.0;
    int num_threads = 8;
    unsigned int seed = 2;

    // unsigned int init_seed = 2;
    // unsigned int *seeds = (unsigned int *)malloc(num_threads * sizeof(unsigned int));
    // seeds[0] = init_seed;
    // int chunk_size = n / num_threads;
    // unsigned int current_seed = init_seed;
    // for (int i = 1; i < num_threads; ++i) {
    //     for (int j = 0; j < chunk_size; ++j) {
    //         rand_r(&current_seed);
    //     }
    //     seeds[i] = current_seed;
    // }
    // #pragma omp parallel for num_threads(num_threads)
    // for(int i = 0; i < n; ++i) {
    //     int thread_id = omp_get_thread_num();
    //     unsigned int local_seed = seeds[thread_id];
    //     for(int j = 0; j < i - thread_id * chunk_size; ++j) {
    //         rand_r(&local_seed);
    //     }
    //     array[i] = ((float)rand_r(&local_seed) / (float)(RAND_MAX)) * a;
    // }
    // free(seeds);

    # pragma omp parallel for private(seed) num_threads(num_threads)
    for(i=0; i<n; ++i) {
        array[i] = ((float)rand_r(&seed)/(float)(RAND_MAX)) * a;
        // array[i] = ((float)rand()/(float)(RAND_MAX)) * a;
    }
        
}

int main(int argc, char* argv[])
{    
    int n = strtol(argv[1], NULL, 10);
    int num_bins = 10;
    float *array;
    int *bins;
    array = (float *)malloc(sizeof(float) * n);
    bins = (int*)malloc(sizeof(int) * num_bins);

    double start1 = omp_get_wtime();
    generate_random_numbers(array, n);
    parallel_histogram(array, n, bins, num_bins);
    double end1 = omp_get_wtime();
    double start2 = omp_get_wtime();
    
    double end2 = omp_get_wtime();
    
    printf("Results\n");
    int i;
    for (i = 0; i < num_bins; i++) {
        printf("bins[%d]: %d\n", i, bins[i]);
    }
    printf("Running time: %f seconds\n", end1 - start1);
    // printf("Running time: %f seconds\n", end2 - start2);
    free(array);
    free(bins);

    return 0;
}

